Utforsk avanserte TypeScript-teknikker med template literals for kraftfull manipulering av string-typer. Lær å parse, transformere og validere strengbaserte typer effektivt.
Parsing av Template Literals i TypeScript: Avansert Manipulering av String-typer
Type-systemet i TypeScript gir kraftige verktøy for å manipulere og validere data ved kompileringstid. Blant disse verktøyene tilbyr template literals en unik tilnærming til manipulering av string-typer. Denne artikkelen dykker ned i de avanserte aspektene ved parsing av template literals, og viser hvordan man kan lage sofistikert logikk på typenivå for strengbaserte data.
Hva er Template Literal Types?
Template literal types, introdusert i TypeScript 4.1, lar deg definere string-typer basert på string literals og andre typer. De bruker backticks (`) for å definere typen, likt som template literals i JavaScript.
For eksempel:
type Color = "red" | "green" | "blue";
type Shade = "light" | "dark";
type ColorCombination = `${Shade} ${Color}`;
// ColorCombination er nå "light red" | "light green" | "light blue" | "dark red" | "dark green" | "dark blue"
Denne tilsynelatende enkle funksjonen åpner for et bredt spekter av muligheter for strengbehandling ved kompileringstid.
Grunnleggende Bruk av Template Literal Types
Før vi dykker ned i avanserte teknikker, la oss se på noen grunnleggende bruksområder.
Sammenslåing av String Literals
Du kan enkelt kombinere string literals og andre typer for å lage nye string-typer:
type Greeting = `Hello, ${string}!`;
// Eksempel på bruk
const greet = (name: string): Greeting => `Hello, ${name}!`;
const message: Greeting = greet("World"); // Gyldig
const invalidMessage: Greeting = "Goodbye, World!"; // Feil: Typen '"Goodbye, World!"' kan ikke tilordnes typen '`Hello, ${string}!`'.
Bruk av Union Types
Union types lar deg definere en type som en kombinasjon av flere mulige verdier. Template literals kan inkludere union types for å generere mer komplekse string-type unions:
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = `/api/users` | `/api/products`;
type Route = `${HTTPMethod} ${Endpoint}`;
// Route er nå "GET /api/users" | "POST /api/users" | "PUT /api/users" | "DELETE /api/users" | "GET /api/products" | "POST /api/products" | "PUT /api/products" | "DELETE /api/products"
Avanserte Teknikker for Parsing av Template Literals
Den virkelige kraften i template literal types ligger i deres evne til å kombineres med andre avanserte TypeScript-funksjoner, som betingede typer og type inference, for å parse og manipulere string-typer.
Inferens av Deler av en String-type
Du kan bruke infer-nøkkelordet i en betinget type for å trekke ut spesifikke deler av en string-type. Dette er grunnlaget for parsing av string-typer.
Vurder en type som trekker ut filendelsen fra et filnavn:
type GetFileExtension = T extends `${string}.${infer Extension}` ? Extension : never;
// Eksempler
type Extension1 = GetFileExtension<"myFile.txt">; // "txt"
type Extension2 = GetFileExtension<"anotherFile.image.jpg">; // "image.jpg" (tar den siste endelsen)
type Extension3 = GetFileExtension<"noExtension">; // never
I dette eksempelet sjekker den betingede typen om input-typen T matcher mønsteret ${string}.${infer Extension}. Hvis den gjør det, infererer den delen etter det siste punktumet til Extension-typevariabelen, som deretter returneres. Ellers returnerer den never.
Parsing med Flere Inferenser
Du kan bruke flereinfer-nøkkelord i samme template literal for å trekke ut flere deler av en string-type samtidig.
type ParseConnectionString =
T extends `${infer Protocol}://${infer Host}:${infer Port}` ?
{ protocol: Protocol, host: Host, port: Port } : never;
// Eksempel
type Connection = ParseConnectionString<"http://localhost:3000">;
// { protocol: "http", host: "localhost", port: "3000" }
type InvalidConnection = ParseConnectionString<"invalid-connection">; // never
Denne typen parser en tilkoblingsstreng til dens protokoll-, host- og port-komponenter.
Rekursive Typedefinisjoner for Kompleks Parsing
For mer komplekse strengstrukturer kan du bruke rekursive typedefinisjoner. Dette lar deg gjentatte ganger parse deler av en string-type til du når et ønsket resultat.
La oss si at du vil dele en streng inn i en array av individuelle tegn på typenivå. Dette er betydelig mer avansert.
type StringToArray =
T extends `${infer Char}${infer Rest}`
? StringToArray
: Acc;
// Eksempel
type MyArray = StringToArray<"hello">; // ["h", "e", "l", "l", "o"]
Forklaring:
StringToArray<T extends string, Acc extends string[] = []>: Definerer en generisk type kaltStringToArraysom tar en string-typeTsom input og en valgfri akkumulatorAccsom standard er en tom string-array. Akkumulatoren vil lagre tegnene mens vi prosesserer dem.T extends `${infer Char}${infer Rest}`: Dette er den betingede typesjekken. Den sjekker om input-strengenTkan deles inn i et første tegnCharog den resterende strengenRest.infer-nøkkelordet brukes for å fange opp disse delene.StringToArray<Rest, [...Acc, Char]>: Hvis oppdelingen lykkes, kaller vi rekursivtStringToArraymedRestav strengen og en ny akkumulator. Den nye akkumulatoren lages ved å spre den eksisterendeAccog legge til det nåværende tegnetCharpå slutten. Dette legger effektivt til tegnet i den akkumulerende arrayen.Acc: Hvis strengen er tom (den betingede typen feiler, som betyr at det ikke er flere tegn), returnerer vi den akkumulerte arrayenAcc.
Dette eksempelet demonstrerer kraften i rekursjon for å manipulere string-typer. Hvert rekursive kall skreller av ett tegn og legger det til i arrayen til strengen er tom.
Arbeid med Skilletegn
Template literals kan enkelt brukes med skilletegn for å parse strenger. La oss si at du vil trekke ut ord separert med komma.
type SplitString =
T extends `${infer First}${D}${infer Rest}`
? [First, ...SplitString]
: [T];
// Eksempel
type Words = SplitString<"apple,banana,cherry", ",">; // ["apple", "banana", "cherry"]
Denne typen deler strengen rekursivt ved hver forekomst av skilletegnet D.
Praktiske Anvendelser
Disse avanserte teknikkene for parsing av template literals har mange praktiske anvendelser i TypeScript-prosjekter.
Datavalidering
Du kan validere strengbaserte data mot spesifikke mønstre ved kompileringstid. For eksempel, validering av e-postadresser, telefonnumre eller kredittkortnumre. Denne tilnærmingen gir tidlig tilbakemelding og reduserer kjøretidsfeil.
Her er et eksempel på validering av et forenklet e-postadresseformat:
type EmailFormat = `${string}@${string}.${string}`;
const validateEmail = (email: string): email is EmailFormat => {
// I virkeligheten ville en mye mer kompleks regex blitt brukt for korrekt e-postvalidering.
// Dette er kun for demonstrasjonsformål.
return /.+@.+\..+/.test(email);
}
const validEmail: EmailFormat = "user@example.com"; // Gyldig
const invalidEmail: EmailFormat = "invalid-email"; // Typen 'string' kan ikke tilordnes typen '`${string}@${string}.${string}`'.
if(validateEmail(validEmail)) {
console.log("Gyldig e-post");
}
if(validateEmail("invalid-email")) {
console.log("Dette vil ikke skrives ut.");
}
Selv om kjøretidsvalidering med regex fortsatt er nødvendig i tilfeller der typesjekkeren ikke fullt ut kan håndheve begrensningen (f.eks. ved håndtering av ekstern input), gir EmailFormat-typen et verdifullt første forsvarslinje ved kompileringstid.
Generering av API-endepunkter
Template literals kan brukes til å generere API-endepunktstyper basert på en base-URL og et sett med parametere. Dette kan bidra til å sikre konsistens og typesikkerhet når man jobber med API-er.
type BaseURL = "https://api.example.com";
type Resource = "users" | "products";
type ID = string | number;
type GetEndpoint = `${BaseURL}/${T}/${U}`;
// Eksempler
type UserEndpoint = GetEndpoint<"users", 123>; // "https://api.example.com/users/123"
type ProductEndpoint = GetEndpoint<"products", "abc-456">; // "https://api.example.com/products/abc-456"
Kodegenerering
I mer avanserte scenarioer kan template literal-typer brukes som en del av kodegenereringsprosesser. For eksempel, generering av SQL-spørringer basert på et skjema eller oppretting av UI-komponenter basert på en konfigurasjonsfil.
Internasjonalisering (i18n)
Template literals kan være verdifulle i i18n-scenarioer. For eksempel, tenk på et system der oversettelsesnøkler følger en spesifikk navnekonvensjon:
type SupportedLanguages = 'en' | 'es' | 'fr';
type TranslationKeyPrefix = 'common' | 'product' | 'checkout';
type TranslationKey = `${TPrefix}.${string}`;
// Eksempel på bruk:
const getTranslation = (key: TranslationKey, lang: SupportedLanguages): string => {
// Simulerer henting av oversettelsen fra en ressursfil basert på nøkkel og språk
const translations: Record> = {
'common.greeting': {
en: 'Hello',
es: 'Hola',
fr: 'Bonjour',
},
'product.description': {
en: 'A fantastic product!',
es: '¡Un producto fantástico!',
fr: 'Un produit fantastique !',
},
};
const translation = translations[key]?.[lang];
return translation || `Oversettelse ikke funnet for nøkkel: ${key} på språket: ${lang}`;
};
const englishGreeting = getTranslation('common.greeting', 'en'); // Hello
const spanishDescription = getTranslation('product.description', 'es'); // ¡Un producto fantástico!
const unknownTranslation = getTranslation('nonexistent.key' as TranslationKey, 'en'); // Oversettelse ikke funnet for nøkkel: nonexistent.key på språket: en
TranslationKey-typen sikrer at alle oversettelsesnøkler følger et konsistent format, noe som forenkler prosessen med å administrere oversettelser og forhindre feil.
Begrensninger
Selv om template literal-typer er kraftige, har de også begrensninger:
- Kompleksitet: Kompleks parsingslogikk kan raskt bli vanskelig å lese og vedlikeholde.
- Ytelse: Utstrakt bruk av template literal-typer kan påvirke ytelsen ved kompileringstid, spesielt i store prosjekter.
- Mangler i typesikkerhet: Som demonstrert i eksempelet med e-postvalidering, er sjekker ved kompileringstid noen ganger ikke nok. Kjøretidsvalidering er fortsatt nødvendig i tilfeller der eksterne data må overholde strenge formater.
Beste Praksis
For å bruke template literal-typer effektivt, følg disse beste praksisene:
- Hold det enkelt: Bryt ned kompleks parsingslogikk i mindre, håndterbare typer.
- Dokumenter typene dine: Dokumenter tydelig formålet med og bruken av dine template literal-typer.
- Test typene dine: Lag enhetstester for å sikre at typene dine oppfører seg som forventet.
- Balanse mellom kompileringstids- og kjøretidsvalidering: Bruk template literal-typer for grunnleggende validering og kjøretidssjekker for mer komplekse scenarioer.
Konklusjon
TypeScript template literal-typer gir en kraftig og fleksibel måte å manipulere string-typer ved kompileringstid. Ved å kombinere template literals med betingede typer og type inference, kan du lage sofistikert logikk på typenivå for parsing, validering og transformering av strengbaserte data. Selv om det er begrensninger å ta hensyn til, kan fordelene med å bruke template literal-typer når det gjelder typesikkerhet og vedlikehold av kode være betydelige.
Ved å mestre disse avanserte teknikkene kan utviklere lage mer robuste og pålitelige TypeScript-applikasjoner.
Videre Utforskning
For å utdype din forståelse av template literal-typer, vurder å utforske følgende emner:
- Mapped Types: Lær hvordan du transformerer objekttyper basert på template literal-typer.
- Utility Types: Utforsk innebygde TypeScript utility-typer som kan brukes sammen med template literal-typer.
- Advanced Conditional Types: Dykk dypere inn i mulighetene med betingede typer for mer kompleks logikk på typenivå.